﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
namespace Viper
{
    public class Opcodes
    {
        public static byte Call = 3, Bre = 4, Bgt = 5, Jmp = 6, Blt = 7, Bne = 8, PushString = 9, Resb = 10, Ldstr = 11, Wri = 12, Read = 13, Pushbyte = 15, Throw = 15, Try = 16, Catch = 17, SysfPrint = 18, Ret = 19, Halt = 20, Add = 21, Sub = 22, Mul = 23, Div = 24, And = 25, Not = 26, stb = 27, pushword = 28, pushdword = 29, ldbyte = 33, ldword = 34, lddword = 35, sysf_create_win = 36, sysf_create_button = 37, sysf_getmsg = 38, sysf_win_add = 39, sysf_clr_msg = 40, sysf_get_msg = 41, call_this = 42, call_instance = 43, newobj = 44, stoinstance = 45, Ldistr = 46, str2int = 47, int2str = 48, Pop = 49,Set = 50,Get = 51,Stloc = 52,Ldloc = 53,StlocPtr = 54,LdlocPtr = 55,ldptr = 56,Ldobj = 57,Pushobj = 58,Stptr = 59,byte2int = 60,readarr = 61,val2str = 62,laddr = 63,Or = 64,Exnor = 65,Cmpgt = 66,Cmplt = 67;
    }

    public class Assembler
    {
        private bool Global = false;
        public Assembly assembly = new Assembly();
        public void Assemble(string code)
        {    
            var sw = new Stopwatch();

            sw.Start();
            code = code.Replace("\r", "");
            vClass currentClass = null;
            code = code + "\nendp";
            code = code.Replace("\r", "");
            bool entryp = false;
            string headers = "";
            foreach (string c in code.Split('\n'))
            {
                string cmd = c.Trim().Replace("\r", "");
                if (cmd.StartsWith("%include "))
                {
                    StreamReader sr = new StreamReader(cmd.Substring(9));
                    headers += sr.ReadToEnd().Replace("\r", "") + "\n";
                    sr.Close();
                }
            }

            code = headers += "\n" + code;
            
            int vaddr = 0;
            List<varible> Vars = new List<varible>();
           ;
            foreach (string c in code.Split('\n'))
            {
                string cmd = c.Trim().Replace("\r", "");
                if (cmd.StartsWith("proc "))
                {
                    if (!Global)
                    {
                        currentClass.LabelStart(cmd.Substring(5),true);
                    }
                    else
                    {
                        currentClass.LabelStart(cmd.Substring(5));
                        Global = false;
                    }
                    if (entryp)
                    {
                        currentClass.CodeStart = currentClass.pos;
                        entryp = false;
                    }
                }
                else if (cmd == "add")
                {
                    currentClass.Emit(Opcodes.Add);
                }
                else if (cmd == "mul")
                {
                    currentClass.Emit(Opcodes.Mul);
                }
                else if (cmd == "val2str")
                {
                    currentClass.Emit(Opcodes.val2str);
                }
                else if (cmd == "div")
                {
                    currentClass.Emit(Opcodes.Div);
                }
                else if (cmd == "exnor")
                {
                    currentClass.Emit(Opcodes.Exnor);
                }
                else if (cmd == "and")
                {
                    currentClass.Emit(Opcodes.And);
                }
                else if (cmd == "or")
                {
                    currentClass.Emit(Opcodes.Or);
                }
                else if (cmd == "not")
                {
                    currentClass.Emit(Opcodes.Not);
                }
                else if (cmd == "cmpgt")
                {
                    currentClass.Emit(Opcodes.Cmpgt);
                }
                else if (cmd == "cmplt")
                {
                    currentClass.Emit(Opcodes.Cmplt);
                }
                else if (cmd == "read")
                {
                    currentClass.Emit(Opcodes.readarr);
                }
                else if (cmd == "byte2int")
                {
                    currentClass.Emit(Opcodes.byte2int);
                }
                else if (cmd == "stptr")
                {
                    currentClass.Emit(Opcodes.Stptr);
                }
                else if (cmd == "laddr")
                {
                    currentClass.Emit(Opcodes.laddr);
                }
                else if (cmd == "stloc")
                {
                    currentClass.Emit(Opcodes.StlocPtr);
                }
                else if (cmd == "ldloc")
                {
                    currentClass.Emit(Opcodes.LdlocPtr);
                }

                else if (cmd.StartsWith("externlib "))
                {
                    cmd = cmd.Substring(cmd.IndexOf("'") + 1);
                    cmd = cmd.Substring(0, cmd.LastIndexOf("'"));
                    Assembly asm = new Assembly();
                    asm.LoadGEX(cmd);
                    foreach (vClass v in asm.Classes)
                    {
                        this.assembly.Classes.Add(v);
                    }
                    asm = null;
                }
                else if (cmd.StartsWith("externdll "))
                {
                    cmd = cmd.Substring(cmd.IndexOf("'") + 1);
                    cmd = cmd.Substring(0, cmd.LastIndexOf("'"));
                    Assembly asm = new Assembly();
                    asm.LoadGEX(cmd);
                    this.assembly.DLLs.Add(cmd);
                    foreach (vClass v in asm.Classes)
                    {
                        v.ReadMetaData(); // Imports all symbols and there address from each class inside the .GEX file for dynamic linking
                    }
                    asm = null;
                }
                else if (cmd == "sub")
                {
                    currentClass.Emit(Opcodes.Sub);
                }
                else if (cmd.StartsWith("ldloc "))
                {
                    string locinx = cmd.Substring(cmd.LastIndexOf(' ') + 1);
                    currentClass.Emit(Opcodes.Ldloc);
                    currentClass.Emit(Int32.Parse(locinx));
                }
                else if (cmd.StartsWith("stloc "))
                {
                    string locinx = cmd.Substring(cmd.LastIndexOf(' ') + 1);
                    currentClass.Emit(Opcodes.Stloc);
                    currentClass.Emit(Int32.Parse(locinx));
                }
                else if (cmd.StartsWith("ldptr "))
                {
                    string locinx = cmd.Substring(cmd.LastIndexOf(' ') + 1);
                    currentClass.Emit(Opcodes.ldptr);
                    currentClass.Emit(Int32.Parse(locinx));
                }
                else if (cmd.StartsWith("."))
                {
                    currentClass.LabelStart(cmd.Substring(1), true);

                }
                else if (cmd == "ret")
                {
                    currentClass.Emit(Opcodes.Ret);
                    currentClass.Emit(Opcodes.Ret);
                }
                else if (cmd == "try")
                {
                    currentClass.BeginTryBlock();
                }
                else if (cmd == "end")
                {
                    currentClass.EndTry();
                }
                else if (cmd == "sysf_win_add")
                {
                    currentClass.Emit(Opcodes.sysf_win_add);
                }
                else if (cmd.StartsWith("sto "))
                {
                    string s = cmd.Substring(4);
                    string name = s.Substring(0);

                    foreach (varible v in Vars)
                    {
                        if (v.Name == name)
                        {
                            currentClass.Emit(Opcodes.stb, v.address);

                            break;
                        }
                    }
                }
                else if (cmd.StartsWith("class "))
                {
                    currentClass = new vClass(cmd.Substring(6));
                    currentClass.Name = cmd.Substring(6);
                }
                else if (cmd.Replace(" ", "") == "endclass")
                {

                    assembly.addClass(currentClass);
                    //vClass.absPos += currentClass.pos;
                    currentClass = null;
                }
                else if (cmd == "catch")
                {
                    currentClass.Catch();
                }
                else if (cmd == "throw exception")
                {
                    currentClass.Emit(Opcodes.Throw);
                }
                else if (cmd.StartsWith("call "))
                {
                    if (cmd.Contains("::"))
                    {
                        cmd = cmd.Substring(cmd.LastIndexOf(' ') + 1);
                        string Class = cmd.Substring(0, cmd.IndexOf(':'));
                        string Method = cmd.Substring(cmd.LastIndexOf(':') + 1);
                        currentClass.Jmp(Method, Opcodes.Call, Class);
                    }
                    else
                        currentClass.Jmp(cmd.Substring(5), Opcodes.Call, currentClass.Name);
                }
                else if (cmd.StartsWith("callinstance"))
                {
                    if (cmd.Contains("::"))
                    {
                        cmd = cmd.Substring(cmd.LastIndexOf(' ') + 1);
                        string Class = cmd.Substring(0, cmd.IndexOf(':'));
                        string Method = cmd.Substring(cmd.LastIndexOf(':') + 1);
                        currentClass.Jmp(Method, Opcodes.call_instance, Class);
                    }
                }
                else if (cmd.StartsWith("get "))
                {
                    if (cmd.Contains("::"))
                    {
                        cmd = cmd.Substring(cmd.LastIndexOf(' ') + 1);
                        string Class = cmd.Substring(0, cmd.IndexOf(':'));
                        string Name = cmd.Substring(cmd.LastIndexOf(':') + 1);
                        currentClass.Emit(Opcodes.Get);
                        currentClass.Emit(Class);
                        currentClass.Emit(Name);
                    }
                }
                else if (cmd.StartsWith("set "))
                {
                    if (cmd.Contains("::"))
                    {
                        cmd = cmd.Substring(cmd.LastIndexOf(' ') + 1);
                        string Class = cmd.Substring(0, cmd.IndexOf(':'));
                        string Name = cmd.Substring(cmd.LastIndexOf(':') + 1);
                        currentClass.Emit(Opcodes.Set);
                        currentClass.Emit(Class);
                        currentClass.Emit(Name);

                    }
                }
                else if (cmd.StartsWith("jmp "))
                {

                    currentClass.Jmp(cmd.Substring(4), Opcodes.Jmp);
                }
                else if (cmd.StartsWith("%include "))
                {

                }
                else if (cmd == "sysf_inp")
                {
                    currentClass.Emit(Opcodes.Read);
                }
                else if (cmd == "sysf_clr_msg")
                {
                    currentClass.Emit(Opcodes.sysf_clr_msg);
                }

                else if (cmd.StartsWith("bre "))
                {
                    currentClass.Jmp(cmd.Substring(4), Opcodes.Bre);
                }
                else if (cmd.StartsWith("blt "))
                {
                    currentClass.Jmp(cmd.Substring(4), Opcodes.Blt);
                }
                else if (cmd.StartsWith("bgt "))
                {
                    currentClass.Jmp(cmd.Substring(4), Opcodes.Bgt);
                }
                else if (cmd.StartsWith("resb "))
                {
                    string s = cmd.Substring(5);
                    s = s.Replace(" ", "");
                    string name = s.Substring(0, s.IndexOf(','));
                    int size = int.Parse(s.Substring(s.IndexOf(',') + 1));
                    varible v = new varible();
                    v.Name = name;
                    v.address = vaddr;
                    vaddr += size;
                    Vars.Add(v);

                    currentClass.Emit(Opcodes.Resb, size);

                }
                else if (cmd.StartsWith("dword "))
                {
                    string s = cmd.Substring(6);
                    s = s.Replace(" ", "");
                    string name = s;
                    varible v = new varible();
                    v.Name = name;
                    v.address = vaddr;
                    vaddr += 4;
                    Vars.Add(v);
                    currentClass.Emit(Opcodes.Resb, 4);
                }
                else if (cmd.StartsWith("ldstr "))
                {
                    foreach (varible v in Vars)
                    {
                        if (cmd.EndsWith(" " + v.Name))
                        {
                            currentClass.Emit(Opcodes.Ldstr, v.address);
                            break;
                        }
                    }
                }
                else if (cmd.StartsWith("bne "))
                {
                    currentClass.Jmp(cmd.Substring(4), Opcodes.Bne);
                }
                else if (cmd.StartsWith("newobj "))
                {
                    cmd = cmd.Replace(" ", "");
                    cmd = cmd.Substring(6);
                    string name = cmd.Substring(0, cmd.IndexOf(','));
                    string base_class = cmd.Substring(cmd.IndexOf(',') + 1);
                    currentClass.Emit(Opcodes.newobj);
                    currentClass.Code.Write(currentClass.pos, name);
                    currentClass.pos += (uint)(name.Length + 1);
                    currentClass.Code.Write(currentClass.pos, base_class);
                    currentClass.pos += (uint)(base_class.Length + 1);
                }
                else if (cmd.StartsWith("declare "))
                {
                    currentClass.DeclareString(cmd.Substring(cmd.LastIndexOf(' ') + 1));
                }
                else if (cmd.StartsWith("push string"))
                {
                    cmd = cmd.Replace(@"\n", "\n");
                    int id = cmd.IndexOf('\'');
                    string str = cmd.Substring(id + 1) + ((char)0);
                    int i = str.LastIndexOf('\'');
                    str = str.Substring(0, i);
                    currentClass.Emit(Opcodes.PushString, str);
                }
                else if (cmd.Replace(" ", "").StartsWith("pushdword"))
                {
                    currentClass.Emit(Opcodes.pushdword, Int32.Parse(cmd.Substring(cmd.LastIndexOf(' ') + 1)));
                }
                else if (cmd.StartsWith("push byte"))
                {
                    currentClass.Emit(Opcodes.Pushbyte, Byte.Parse(cmd.Substring(cmd.LastIndexOf(' ') + 1)));
                }
                else if (cmd.StartsWith("push object"))
                {
                    currentClass.Emit(Opcodes.Pushobj, cmd.Substring(cmd.LastIndexOf(' ') + 1));

                }
                else if (cmd.StartsWith("ldobj"))
                {
                    currentClass.Emit(Opcodes.Ldobj, cmd.Substring(cmd.LastIndexOf(' ') + 1));

                }
                else if (cmd == "sysf_get_msg")
                {
                    currentClass.Emit(Opcodes.sysf_get_msg);
                }
                else if (cmd == "pop")
                {
                    currentClass.Emit(Opcodes.Pop);
                }

                else if (cmd == "sysf_print")
                {
                    currentClass.Emit(Opcodes.SysfPrint);
                }
                else if (cmd == "halt")
                {
                    currentClass.Emit(Opcodes.Halt);
                }

                else if (cmd == "sysf_create_win")
                {
                    currentClass.Emit(Opcodes.sysf_create_win);
                }
                else if (cmd == "global:")
                {
                    Global = true;
                }
                else if (cmd.StartsWith("%entrypoint"))
                {
                    this.assembly.Entrypoint = (int)currentClass.Code.pos;
                }
            }
            foreach (vClass c in this.assembly.Classes)
            {
                c.End();
            }
            
            Console.WriteLine("Done in:" + sw.ElapsedMilliseconds.ToString());
            
        }
        public Assembler()
        {

        }
    }
}
